package com.atguigu.gmall.item.service.impl;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.feign.product.ProductSkuDetailFeignClient;
import com.atguigu.gmall.feign.search.SearchFeignClient;
import com.atguigu.gmall.starter.cache.aspect.annotation.MallCache;
import com.atguigu.gmall.starter.cache.service.CacheService;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.product.vo.CategoryTreeVo;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.vo.SkuDetailVo.CategoryViewDTO;

import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.product.vo.SkuDetailVo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @author lfy
 * @Description
 * @create 2022-12-03 11:44
 */
@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    ProductSkuDetailFeignClient skuDetailFeignClient;

    @Autowired
    ThreadPoolExecutor coreExecutor;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    CacheService cacheService;

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    SearchFeignClient searchFeignClient;

    //业务Service只关注业务，增强逻辑由切面负责

    @MallCache(
            cacheKey=RedisConst.SKU_DETAIL_CACHE+"#{#args[0]}",
            bitMapName = RedisConst.SKUID_BITMAP,
            bitMapKey = "#{#args[0]}",
            lockKey = RedisConst.SKU_LOCK+"#{#args[0]}",
            ttl = 7,
            unit = TimeUnit.DAYS
    )
    @Override
    public SkuDetailVo getSkuDetailData(Long skuId)  {
       return getDataFromRpc(skuId);
    }

    @Override
    public void incrHotScore(Long skuId) {
        CompletableFuture.runAsync(()->{
            //1、累积热度
            Long increment = redisTemplate.opsForValue().increment("sku:hotscore:" + skuId);
            if(increment%100 == 0){
                //2、同步给es
                searchFeignClient.updateHotScore(skuId,increment);
            }
        },coreExecutor);

    }


//    public SkuDetailVo getSkuDetailDataWithDistLock(Long skuId) throws InterruptedException {
//        //1、先查缓存
//        SkuDetailVo cache = cacheService.getFromCache(skuId);
//        if(cache != null){
//            //2、缓存有直接返回
//            log.info("缓存命中....");
//            return cache;
//        }
//        log.info("缓存未命中....");
//
//        //3、缓存没有；准备回源。
//        //4、先问bitmap，数据库有没有这个商品；  【防止随机值穿透攻击】
//        boolean contain = cacheService.mightContain(skuId);
//        if(!contain){
//            //bitmap不包含，代表数据库无此数据，直接返回 null
//            return null;
//        }
//        //5、bitmap说有，就去数据库查一查。查之前加锁   【防止缓存击穿】
//        RLock lock = redissonClient.getLock(RedisConst.SKU_LOCK +skuId); //锁的粒度要细；
//        boolean tryLock = false;
//        try {
//            //6、加锁； tryLock尝试竞争一下锁
//            tryLock = lock.tryLock();
//            if(tryLock){
//                //7、抢到锁；回源
//                cache = getDataFromRpc(skuId);
//                //8、放到缓存中
//                cacheService.saveData(skuId,cache);
//                return cache;
//            }else {
//                //9、睡 300ms 直接查缓存即可
//                TimeUnit.MILLISECONDS.sleep(300);
//                return cacheService.getFromCache(skuId);
//            }
//        }finally {
//            if(tryLock) {
//                try {
//                    lock.unlock();
//                }catch (Exception e){}
//            }
//        }
//    }



    //缓存： O(1)
    //本地缓存： 缓存的数据就放在微服务所在的jvm内存中
    private Map<Long, SkuDetailVo> cache = new ConcurrentHashMap<>();
    //JUC：本地锁；在分布式场景，会导致锁不住所有机器；
//    ReentrantLock lock = new ReentrantLock(); //原理：AQS、CAS
//    public SkuDetailVo getSkuDetailDataWithLocalLock(Long skuId) {
//        SkuDetailVo returnVal = null;
//        //1、先查缓存
//        log.info("商品详情查询开始...");
//        returnVal = cacheService.getFromCache(skuId);
//        if(returnVal == null){ //一定要能判定出 真没(null)和假没(x)
//            //2、位图判定是否有
//            boolean contain = cacheService.mightContain(skuId);
//            if(!contain){
//                log.info("bitmap没有，疑似攻击请求，直接打回");
//                return null;
//            }
//            //3、缓存中没有；回源
//            log.info("bitmap有，准备回源。需要加锁防止击穿");
//            //准备拦截击穿风险
//            boolean tryLock = lock.tryLock(); //100w进来瞬间进行抢锁；
//            if(tryLock){
//                log.info("加锁成功...正在回源");
//                //4、抢锁成功
//                returnVal = getDataFromRpc(skuId);
//                //5、保存到缓存； null值缓存都可能不会被调用；
//                //如果这里用的是bloomfilter，可能会误判，即使误判，放给数据库没有也要null值缓存
//                cacheService.saveData(skuId,returnVal);
//                lock.unlock();
//            }else {
//               //6、抢锁失败，500ms 后直接看缓存即可。
//                try {
//                    log.info("加锁失败...稍等直接返回缓存数据");
//                    TimeUnit.MILLISECONDS.sleep(500);
//                    returnVal = cacheService.getFromCache(skuId);
//                } catch (InterruptedException e) {
//                }
//            }
//        }
//        return returnVal;
//    }
//
//    public SkuDetailVo getSkuDetailDataNullSave(Long skuId) {
//        //1、先查缓存   sku:info:49 == 商品json
//        String json = redisTemplate.opsForValue().get("sku:info:" + skuId);
//        if (StringUtils.isEmpty(json)) {
//            //2、缓存没有，回源。
//            synchronized (this){
//                //先看缓存
//                SkuDetailVo result = getDataFromRpc(skuId);
//                String jsonData = "x";
//                //3、放入缓存； 即使是null值也缓存到redis
//                if (result != null){
//                    jsonData = JSON.toJSONString(result); // {}
//                }
//                redisTemplate.opsForValue().set("sku:info:" + skuId,jsonData,7, TimeUnit.DAYS);
//
//                return result;
//            }
//
//        }
//
//        //4、缓存中有；
//        //1）、真有：  json真数据
//        //2）、假有：  x
//        if("x".equals(json)){
//            log.info("疑似攻击请求");
//            return null;
//        }
//        SkuDetailVo result = JSON.parseObject(json, SkuDetailVo.class);
//        return result;
//    }

    /**
     * 查询商品详情
     *
     * @param skuId
     * @return
     */
    public SkuDetailVo getSkuDetailDataLocalCache(Long skuId) {

        //1、先查缓存
        SkuDetailVo result = cache.get(skuId);
        //2、缓存中没有
        if (result == null) {
            log.info("商品详情：缓存未命中，正在回源");
            //3、回源：  回到数据源头进行查询
            result = getDataFromRpc(skuId);
            //4、把数据同步到缓存
            cache.put(skuId, result);
        }else {
            log.info("商品详情：缓存命中");
        }

        return result;
    }

    private SkuDetailVo getDataFromRpc(Long skuId) {
        //获取商品详情数据
        SkuDetailVo data = new SkuDetailVo();
        //1、异步：商品详情【图片】 //自定义的线程池？
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            log.info("详情：skuinfo");
            SkuInfo skuInfo = skuDetailFeignClient.getSkuInfo(skuId).getData();
            return skuInfo;
        }, coreExecutor);

        //2、异步：图片
        CompletableFuture<Void> imageFuture = skuInfoFuture.thenAcceptAsync((res) -> {
            log.info("图片：skuimage");
            if(res == null) return;
            List<SkuImage> skuImages = skuDetailFeignClient.getSkuImages(skuId).getData(); //1s
            res.setSkuImageList(skuImages);
            data.setSkuInfo(res);
        }, coreExecutor);


        //3、异步：当前商品精确完整分类信息； //2s
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync(res -> {
            log.info("分类：category");
            if(res == null) return;
            CategoryTreeVo categoryTreeVo = skuDetailFeignClient.getCategoryTreeWithC3Id(res.getCategory3Id()).getData();
            //数据模型转换
            CategoryViewDTO viewDTO = convertToCategoryViewDTO(categoryTreeVo);
            data.setCategoryView(viewDTO);
        }, coreExecutor);



        //4、异步：实时价格
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            log.info("价格：price");
            try {
                BigDecimal price = skuDetailFeignClient.getPrice(skuId).getData();
                data.setPrice(price);
            }catch (Exception e){

            }

        }, coreExecutor);


        //5、销售属性;
        CompletableFuture<Void> saleAttrFuture = skuInfoFuture.thenAcceptAsync(res -> {
            log.info("销售属性：saleAttr");
            if(res == null) return;
            List<SpuSaleAttr> spuSaleAttrs = skuDetailFeignClient.getSpuSaleAttr(res.getSpuId(), skuId).getData();
            data.setSpuSaleAttrList(spuSaleAttrs);
        }, coreExecutor);


        //6、当前sku的所有兄弟们的所有组合可能性。
        CompletableFuture<Void> valueJsonFuture = skuInfoFuture.thenAcceptAsync(res -> {
            log.info("组合：valueJson");
            if(res == null) return;
            String json = skuDetailFeignClient.getValuesSkuJson(res.getSpuId()).getData();
            data.setValuesSkuJson(json);
        }, coreExecutor);

        //等待所有任务都完成
        CompletableFuture
                .allOf(valueJsonFuture, saleAttrFuture, priceFuture, categoryFuture, imageFuture)
                .join();
        return data;
    }

    private CategoryViewDTO convertToCategoryViewDTO(CategoryTreeVo categoryTreeVo) {
        CategoryViewDTO viewDTO = new CategoryViewDTO();
        viewDTO.setCategory1Id(categoryTreeVo.getCategoryId());
        viewDTO.setCategory1Name(categoryTreeVo.getCategoryName());

        CategoryTreeVo child1 = categoryTreeVo.getCategoryChild().get(0);
        viewDTO.setCategory2Id(child1.getCategoryId());
        viewDTO.setCategory2Name(child1.getCategoryName());

        CategoryTreeVo child2 = child1.getCategoryChild().get(0);
        viewDTO.setCategory3Id(child2.getCategoryId());
        viewDTO.setCategory3Name(child2.getCategoryName());
        return viewDTO;
    }
}
